/*
 * Integrated Rule Inference System (IRIS):
 * An extensible rule inference system for datalog with extensions.
 * 
 * Copyright (C) 2008 Semantic Technology Institute (STI) Innsbruck, 
 * University of Innsbruck, Technikerstrasse 21a, 6020 Innsbruck, Austria.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA  02110-1301, USA.
 */
package org.deri.iris.rules.compiler;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.deri.iris.Configuration;
import org.deri.iris.EvaluationException;
import org.deri.iris.api.basics.IPredicate;
import org.deri.iris.api.basics.ITuple;
import org.deri.iris.api.terms.ITerm;
import org.deri.iris.api.terms.IVariable;
import org.deri.iris.factory.Factory;
import org.deri.iris.facts.IFacts;
import org.deri.iris.storage.IRelation;
import org.deri.iris.storage.simple.SimpleRelationFactory;

import Top1.EquationTopK;
import Top1.EquationTopK2;
import TopKBasics.Atom;
import TopKBasics.Body;
import TopKBasics.KeyMap;
import TopKBasics.KeyMap2;

/**
 * A compiled rule.
 */
public class CompiledRule2 implements ICompiledRule
{
	/**
	 * Constructor.
	 * @param elements The rule elements produced by the rule compiler.
	 * @param headPredicate The head predicate of the original rule.
	 */
	public CompiledRule2( List<RuleElement> elements, IPredicate headPredicate, Configuration configuration/*, double weight*/ )
	{
		assert elements.size() > 0;
		assert configuration != null;
		
		mConfiguration = configuration;
		
		mHeadPredicate = headPredicate;
		
		mElements = elements;
		
		//Amir added
		mWeight = 1;// temporary...
	}

	/**
	 * Evaluate the rule.
	 * Each element is called in turn to produce tuples to pass on to the next rule element.
	 * If any rule element outputs an empty relation, then stop.
	 * @throws EvaluationException 
	 */
	public IRelation evaluate(boolean firstIteration) throws EvaluationException
	{
		// The first literal receives the starting relation (which has one zero length tuple in it). */
		IRelation output = mStartingRelation;
		
		for( RuleElement element : mElements )
		{
			output = element.process( this, output );
			
			// Must always get some output relation, even if it is empty.
			assert output != null;
			
			//Amir added
			if ( element.equals(mElements.get(mElements.size() - 2)) ) 
			{
				ConvertToAtoms(output, firstIteration);
			}
			
			// All literals are conjunctive, so if any literal produces no results,
			// then the whole rule produces no results.
			if( output.size() == 0 )
				break;
		}
		
		return output;
	}
	
	public IRelation evaluateIteratively( IFacts deltas ) throws EvaluationException
	{
		IRelation union = mConfiguration.relationFactory.createRelation();
	
		/*
		for each literal (rule element) for which there exists a delta substitution
			substitute the rule element with the delta
			evaluate the whole rule
			store the results
		combine all the results and return
		*/
		for( int r = 0; r < mElements.size(); ++r )
		{
			RuleElement original = mElements.get( r );
			
			RuleElement substitution = original.getDeltaSubstitution( deltas );
			
			if( substitution != null )
			{
				//Amir added
				substitution.setIndices(original.getIndices());
				
				mElements.set( r, substitution );
	
				// Now just evaluate the modified rule
				IRelation output = evaluate(false);
				
				for( int t = 0; t < output.size(); ++t )
					union.add( output.get( t ) );
				
				// Put the original rule element back the way it was
				mElements.set( r, original );
			}
		}
		
		return union;
	}
	
	
	
	public IPredicate headPredicate()
    {
	    return mHeadPredicate;
    }

	public double getWeight()
	{
		return mWeight;
	}
	
	
	public List<IVariable> getVariablesBindings()
	{
		if( mElements.size() > 0 )
			return mElements.get( mElements.size() - 1 ).getOutputVariables();
		else
			return new ArrayList<IVariable>();
	}
	
	//Amir added
	public List<RuleElement> getElements()
	{
		return mElements;
	}
	
	
	//Amir added
	public void ConvertToAtoms (IRelation output, boolean firstIteration) throws EvaluationException
	{
		List<ITuple> heads;
		List<List<ITuple>> bodies;
		
		if (true == firstIteration) 
		{
			long startTime = System.currentTimeMillis();
			bodies = getAtomBody(output, firstIteration);
			long endTime = System.currentTimeMillis();
			double durationFullProv = (endTime - startTime);
	        //System.out.println("Time for finding bodies: " + durationFullProv);
	        
	        startTime = System.currentTimeMillis();
			heads = getHeads(output, firstIteration);
			endTime = System.currentTimeMillis();
			durationFullProv = (endTime - startTime);
	        //System.out.println("Time for finding heads: " + durationFullProv);
	        //System.out.println("num of heads: " + heads.size());
	        startTime = System.currentTimeMillis();
			for (int i = 0; i < heads.size(); i++) 
			{
				HandleTop1Scenario(heads.get( i ), bodies.get( i ));
			}
			endTime = System.currentTimeMillis();
			durationFullProv = (endTime - startTime);
			//System.out.println("Time for top1: " + durationFullProv);
		}
		
		else
		{
			//List<List<String>> bodies =  getStringBody(output, firstIteration);
			long startTime = System.currentTimeMillis();
			bodies = getAtomBody(output, firstIteration);
			long endTime = System.currentTimeMillis();
			double durationFullProv = (endTime - startTime);
	        //System.out.println("Time for finding bodies second iter: " + durationFullProv);
			heads = getHeads(output, firstIteration);
			//System.out.println("heads size: " + heads.size());
			startTime = System.currentTimeMillis();
			for (int i = 0; i < heads.size(); i++) 
			{
				HandleTop1Scenario(heads.get( i ), bodies.get( i ));		        
			}
			endTime = System.currentTimeMillis();
			durationFullProv = (endTime - startTime);
			//System.out.println("Time for top1 second iter: " + durationFullProv);
			System.out.println("size of key map: " + KeyMap.getInstance().Size());
		}
	}
	
	//Amir added
	public ITuple TupleByIndices (int [] indices, ITuple tuple)
	{
		ITerm[] terms = new ITerm[indices.length];
		int index = 0;
		for (int i : indices) 
		{
			terms[index++] = tuple.get(i);
		}
		
		return Factory.BASIC.createTuple(terms);
	}
	
	
	//Amir added
	public void HandleTop1Scenario (ITuple ihead, List<ITuple> ibody)
	{
		if (false == ihead.isTop1Found()) 
		{
			EquationTopK2.UpdateTop1WhileSemiNaive(ihead, ibody);	
		}
	}
	
	
	public List<ITuple> getHeads (IRelation output, boolean firstIteration) throws EvaluationException
	{
		List<ITuple> heads = new ArrayList<ITuple>();
		IRelation finalOutput = mElements.get(mElements.size() - 1).process( this, output );
		//System.out.println("num of tuples: " + output.size());
		String name = mHeadPredicate.getPredicateSymbol();
		//System.out.println("trying to find predicate: " + name);
		int i = 0;
		for ( int t = 0; t < finalOutput.size(); ++t ) 
		{
			ITuple tuple = finalOutput.get(t);
			boolean added = KeyMap2.getInstance().StringUpdate( name, tuple );
			
			if (false == added) 
			{
				heads.add( KeyMap2.getInstance().Get(name, tuple) );
				i++;
			}
			
			else
			{
				heads.add(tuple);
			}
			
			heads.get(heads.size() - 1).setCurRuleWeight(mWeight); // for top-1
			
		}
		//System.out.println("num times to find head in map : " + i);
		return heads;
	}
	
	
	public List<List<ITuple>> getAtomBody (IRelation output, boolean firstIteration)
	{
		List<List<ITuple>> bodies = new ArrayList<List<ITuple>>();
		boolean added = false;
		//System.out.println("started to add bodies");
		for ( int t = 0; t < output.size(); ++t ) 
		{
			ITuple tuple = output.get(t);
			List<ITuple> body = new ArrayList<ITuple>();
			for( int r = 0; r < mElements.size() - 2; ++r )
			{
				RuleElement element = mElements.get( r );
				int [] indices = element.getIndices();
				String name = element.getPredicate().getPredicateSymbol();
				ITuple relevantTuple = TupleByIndices(indices, tuple);
				added = KeyMap2.getInstance().StringUpdate(name, relevantTuple);
				
				if (false == added)
				{
					body.add( KeyMap2.getInstance().Get(name, relevantTuple) );
					body.get(body.size() - 1).setFact(firstIteration);
				}
				
				else
				{
					relevantTuple.setFact(firstIteration);
					body.add(relevantTuple);
				}
			}
			
			bodies.add(body);
		}
		//System.out.println("finished adding bodies");
		return bodies;
	}
	
	/** The starting relation for evaluating each sub-goal. */
	private static final IRelation mStartingRelation = new SimpleRelationFactory().createRelation();

	static
	{
		// Start the evaluation with a single, zero-length tuple.
		mStartingRelation.add( Factory.BASIC.createTuple() );
	}
	
	/** The rule elements in order. */
	private final List<RuleElement> mElements;
	
	/** The head predicate. */
	private final IPredicate mHeadPredicate;
	
	private final Configuration mConfiguration;
	
	//Amir added
	private final double mWeight;
}
